#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <ctime>
#include <thread>
#include <cassert>
#include <ctime>

using namespace std;

//如果多线程访问同一个全局变量，并对它进行数据计算，多线程会互相影响
//线程不断切换，对一个没有保护的全局变量可能会引起数据紊乱
//解决方法：加锁保护：加锁的时候，要保证加锁的力度，越小越好
//全局或者静态的锁
//pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;//phread_mutex_t 就是原生线程库提供的一个数据类型

//原子性的体现
//加了锁之后，线程在临界区内，依然可能会被切换，但不会出现问题
//1.因为虽然被切换了，但这个线程是持有锁被切换的，所以其他抢票线程要执行临界区的代码，也必须先申请锁，
//但其他线程是无法申请成功的，所以其他线程也不会进入临界区，这就保证了临界区中数据的一致性！
//错误的编码方式---一个线程，不申请锁，就单纯的去访问临界资源
//在没有持有锁的线程看来，有意义的情况只有俩种：
//1.线程1没有持有锁(什么都没做) 2.线程1释放锁(做完)，此时它可以申请锁！
//加锁就是串行执行，执行临界区代码一定是串行的
//要访问临界资源，每个线程必须先申请锁，每一个线程必须先看到同一个锁并且访问它，锁本身就是一种共享资源
//为了保证锁的安全，申请和释放锁，必须是原子的，锁自己可以保证
//为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是：
//把寄存器和内存单元的数据相交换, 由于只有一条指令, 保证了原子性
//即使是多处理器平台, 访问内存的总线周期也有先后, 一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。


int tickets = 10000; // 在并发访问的时候，导致了数据不一致的问题！临界资源
#define PTHREAD_NUM 5

class ThreadData
{
public:
    ThreadData(const std::string& name, pthread_mutex_t* pm)
        :
        tname(name),
        pmtx(pm)
    {}
public:
    std::string tname;
    pthread_mutex_t* pmtx;
};

void* getTickets(void* args)
{
    //(void)args;
    ThreadData* td = (ThreadData*)args;
    while (true)
    {
        //抢票逻辑
        int n = pthread_mutex_lock(td->pmtx);
        assert(n == 0);
        //临界区
        if (tickets > 0)//1.判断的本质也是计算
        {
            usleep(rand() % 1500);
            printf("%s: %d\n", td->tname.c_str(), tickets);
            tickets--;//2.也可能出现问题
            //1.读取数据到cpu内的寄存器中 2.cpu内部进行计算-- 3.将结果写回内存中
            n = pthread_mutex_unlock(td->pmtx);
            assert(n == 0);
        }
        else
        {
            n = pthread_mutex_unlock(td->pmtx);
            assert(n == 0);
            break;
        }
    }

    delete td;

    return nullptr;
}

int main()
{
    time_t start = time(nullptr);
    //局部的锁，需要初始化
    pthread_mutex_t mtx;
    pthread_mutex_init(&mtx, nullptr);

    srand((unsigned long)time(nullptr) ^ getpid() ^ 0x12312);
    pthread_t t[PTHREAD_NUM];
    //pthread_t t1, t2, t3;
    // 多线程抢票的逻辑
    for (int i = 0; i < PTHREAD_NUM; i++)
    {
        std::string name = "thread ";
        name += std::to_string(i + 1);
        ThreadData* td = new ThreadData(name, &mtx);
        pthread_create(t + i, nullptr, getTickets, td);
    }

    // pthread_create(&t1, nullptr, getTickets, (void*)"thread 1");
    // pthread_create(&t2, nullptr, getTickets, (void*)"thread 2");
    // pthread_create(&t3, nullptr, getTickets, (void*)"thread 3");

    // pthread_join(t1, nullptr);
    // pthread_join(t2, nullptr);
    // pthread_join(t3, nullptr);

    for (int i = 0; i < PTHREAD_NUM; i++)
    {
        pthread_join(t[i], nullptr);
    }
    
    //局部的锁。使用完需要删除
    pthread_mutex_destroy(&mtx);

    time_t end = time(nullptr);
    cout << "cast: " << (int)(end - start) << "S" << endl;

    return 0;
}
